/**
 * 使用 FatFS 文件系统 实现的 ISO-C 标准文件 API
 *
 * LUA lauxlib 需要 feof(), ungetc()
 */

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>

#include "unios.h"
#include "utils.h"
#include "transceiver.h"
#include "ff.h"

#include "log.h"
#define LOGLEVEL LOG_DEBUG
#define LOGTAG   "ISO-C"
#include "log4app.h"

// 重定义 FILE 数据结构
typedef struct __FILE {
    FIL ff;
} FILE;


#if (defined USE_MICROLIB)

// 使用 MicroLIB 做为标准 C 库的情形
// !!! 注意: 需要在Keil工程选项：Options for Target → Target → 勾选Use MicroLIB !!!
//
// By default, all the stdio functions that interact with a file pointer return an error if called. The
// only exceptions to this are the three standard streams stdin, stdout and stderr.
// You can change this behavior using #pragma import(__use_full_stdio). Use of this pragma
// provides a microlib version of stdio that supports ANSI C, with only the following exceptions:
// • The error and EOF indicators are not supported, so feof() and ferror() return 0.
// • All streams are unbuffered, so setvbuf() and setbuf() fail.
//
// Implement the base function fputc() for all output functions. For example, fprintf(), printf(), fwrite(), fputs(), puts(), putc() and putchar().
// Implement the base function fgetc() for all input functions. For example, fscanf(), scanf(), fread(), read(), fgets(), gets(), getc() and getchar().
//     
// @see <<ARM C and C++ Libraries and Floating-Point Support User Guide>>: Section 2.8 "Tailoring the microlib input/output functions".

// 如果 __use_full_stdio，则只需要 fputc(), fgetc()，但有以下问题:
// - fopen(), fclose() 使用默认实现无法支持 FatFS
// - fwrite(), fread() 使用 fputc(), fgetc() 实现，效率很低
// 所以，还是自己实现全部 标准 C 文件 API 吧 !!!
//#pragma import(__use_full_stdio)

FILE* fopen(const char *path, const char *mode) {
    if(!path || !mode) return NULL;

    FILE *fp = (FILE*)unios_malloc0(sizeof(FILE));
    if(!fp) return NULL;
    
    FRESULT fres = FR_INVALID_PARAMETER;
    if(strcmp(mode, "r") == 0 || strcmp(mode, "r+") == 0) {
    	fres = f_open(&fp->ff, path, FA_READ);
    } else if(strcmp(mode, "a") == 0 || strcmp(mode, "a+") == 0) {
        fres = f_open(&fp->ff, path, FA_OPEN_APPEND);
    } else if(strcmp(mode, "w") == 0) {
    	fres = f_open(&fp->ff, path, FA_WRITE | FA_CREATE_ALWAYS);
    } else if(strcmp(mode, "w+") == 0) {
    	fres = f_open(&fp->ff, path, FA_READ | FA_WRITE | FA_CREATE_ALWAYS);
    }

    if(fres != FR_OK) {
        unios_free(fp);
        return NULL;
    }
    return fp;
}

int fclose(FILE *fp) {
    if(fp == stdin || fp == stdout || fp == stderr) return EOF;
    return (fp && f_close(&fp->ff) == FR_OK) ? 0 : EOF;
}

int feof(FILE *fp) {
    if(fp == stdin || fp == stdout || fp == stderr) return 0;
    if(!fp) return 1;
    return f_eof(&fp->ff);
}

int ferror(FILE *fp) {
    if(fp == stdin || fp == stdout || fp == stderr) return 0;
    if(!fp) return EOF;
    return f_error(&fp->ff);
}

size_t fwrite(const void *buf, size_t size, size_t nmemb, FILE *fp) {
    if(fp == stdout || fp == stderr) {
        transceiver_t *stdtrx = transceiver_get_std();
        size_t bw = 0;
        if(stdtrx && stdtrx->tx(stdtrx, buf, size * nmemb, &bw) == STD_ERR_OK) return bw / size;
    } else if(fp && fp != stdin) {
        UINT bw = 0;
        if(f_write(&fp->ff, buf, size * nmemb, &bw) == FR_OK) return bw / size;
    }
    return 0;
}

size_t fread(void *buf, size_t size, size_t nmemb, FILE *fp) {
    if(fp == stdin) {
        transceiver_t *stdtrx = transceiver_get_std();
        size_t br = 0;
        if(stdtrx && stdtrx->rx(stdtrx, buf, size * nmemb, &br, -1, 0, NULL, RX_FLAG_NULL, NULL, RX_FLAG_NULL) == STD_ERR_OK) return br / size;
    } else if(fp && fp != stdout && fp != stderr) {
        UINT br = 0;
        if(f_read(&fp->ff, buf, size * nmemb, &br) == FR_OK) return br / size;
    }
    return 0;
}

int fputs(const char *str, FILE *fp) {
    return fwrite(str, 1, strlen(str), fp);
}

int fputc(int ch, FILE *fp) {
    return (fwrite(&ch, 1, 1, fp) == 1) ? ch : EOF;
}

int fgetc(FILE *fp) {
    char ch;
    return (fread(&ch, 1, 1, fp) == 1) ? ch : EOF;
}

// A character is virtually put back into an input stream, decreasing its internal file position as if a previous getc operation was undone.
int ungetc(int ch, FILE *fp) {
    return EOF;
}

// The function __backspace() is used by the scanf family of functions, and must be re-implemented if you retarget the stdio arrangements at the fgetc() level.
// It returns to the stream the last character that was read from the stream, so that the same character can be read from the stream again by the next read operation. 
// This means that a character that was read from the stream by scanf but that is not required (that is, it terminates the scanf operation) is read correctly by the next function that reads from the stream.
int __backspace(FILE *fp) {
    return EOF;
}

#endif
